const async = require('async');
const random = require('string-random');

let defaults = {
    limitGroup: 5,
    limitInGroup: 3,
    retry: 3,
    delay: 100,
    tryor: null,//重试参数处理函数
    fetchor: null,//执行函数
};

/**
 * 任务启动入口
 * @param {Array} items 待执行的数组
 * @param {Object} _options 分组配置参数
 *  @return {Object} {succ: [], trys: [], fail: []} 
 */
async function fanner(items, _options) {
    try {
        let options = Object.assign({}, defaults, _options);
        let id = options.id = `[${random()}]`;
        if (!Array.isArray(items) || items.length == 0 || !options.fetchor) {
            throw new Error('args error or empty');
        }
        console.log(`\n${id} started, items.length = ${items.length}, options = ${JSON.stringify(options)}`);
        let results = null, looper = 0;
        while (!results || results.trys.length > 0) {
            looper++;
            options.looper = looper;
            options.startIndex = 0;
            if (looper > 1) {
                options.endIndex = results.trys.length;
                let trys = results.trys;
                if (options.tryor) {
                    trys = options.tryor(trys);
                }
                results = await slicer(trys, options);
            } else {
                options.endIndex = items.length;
                results = await slicer(items, options);
            }
            console.log(`${id} end looper=${looper}, succ.size=${results.succ.length}, trys.size=${results.trys.length}, fail.size=${results.fail.length}\n`);
        }
        return results;
    } catch (error) {
        console.log(error);
    }
}

/**
 * 分组并执行，并获取结果
 * @param {Array} items 待执行列表
 * @param {Object} options 执行参数
 * {number} options.looper 外循环计数器，失败结果重新执行计数器+1
 * {number} options.startIndex 起始序号
 * {number} options.endIndex 结束序号
 * {number} options.limitGroup 最多并发的组数
 * {number} options.limitInGroup 每组内并发限制数
 * {number} options.retry 最大重试次数
 * {number} options.delay 随机延时基数，毫秒值
 * @return {Promise{succ: [], trys: [], fail: []}} 
 */
function slicer(items, options) {
    console.log(`\n${options.id} ----looper = ${options.looper}, prepare for index ${options.startIndex} --> ${options.endIndex}`);
    return new Promise(async function (resolve, reject) {
        let finalResult = { succ: [], trys: [], fail: [] },
            counter = 0,
            count = Math.ceil((options.endIndex - options.startIndex) / options.limitInGroup);
        if (items.length <= 0) {
            reject("silcer itemList empty!");
            return;
        }
        try {
            console.log(`${options.id} 本轮共${items.length}个，每组${options.limitInGroup}个，需要划分${count}组`);
            let paralleArray = [];
            let startIndex = options.startIndex, endIndex;
            while (counter < count) {
				/*
				* 需要注意的是当剩余的任务不足以达到并发数的时候,要保证任务分割不能出界
				*/
                if (startIndex + options.limitInGroup < options.endIndex) {
                    endIndex = startIndex + options.limitInGroup;
                } else {
                    endIndex = options.endIndex;//截取边界
                }
                /*分割任务*/
                let list = items.slice(startIndex, endIndex);
                paralleArray.push(async.asyncify(async.apply(delayer, {
                    counter: counter,
                    startIndex: startIndex,
                    endIndex: endIndex,
                    items: list, //grouper用
                    id: options.id, //grouper用
                    limitInGroup: options.limitInGroup, //grouper用
                    retry: options.retry, //grouper用
                    delay: options.delay, //grouper用
                    fetchor: options.fetchor, //grouper用
                })));//async.asyncify 包裹成异步函数

                counter++;
                startIndex = endIndex;//推进任务进行
            }

            async.parallelLimit(paralleArray, options.limitGroup, function (err, results) {
                if (results && results.length > 0) {
                    results.forEach((value) => {
                        finalResult.succ.push(...value.succ);
                        finalResult.trys.push(...value.trys);
                        finalResult.fail.push(...value.fail);
                    });
                    resolve(finalResult);
                } else {
                    reject('slicer results empty!!!');
                }
            });
        } catch (e) {
            reject(e);
        }
    });//end promise
};

/**
 * 给异步执行的组添加延时
 * @param {Object} options 
 *  {string} options.id
 *  {number} options.counter 
 *  {number} options.startIndex
 *  {number} options.endIndex
 *  {array} options.items 待执行项数组
 *  {number} options.limitInGroup 组内限制并发数
 *  {number} options.retry 重试次数
 *  {number} options.delay 随机延时基数，毫秒值
 *  {function} options.fetchor 最终执行的，执行函数
 */
function delayer(options) {
    return new Promise((resolve, reject) => {
        try {
            //console.log(`----start group = ${options.counter}, index from ${options.startIndex} --> ${options.endIndex}`);
            let delay = options.delay;
            let func = async function () {
                //获得此次任务开始执行的时间
                let startTime = new Date(), time, results = {};
                //进行并发捕获执行命令
                results = await grouper(options).catch(e => {
                    console.log(e);
                    reject(e);
                });//options{items, limitInGroup, retry}
                time = new Date() - startTime;
                console.log(`${options.id} ----end group = ${options.counter}, delay = ${delay}ms, 耗时: ${time}ms`);
                resolve(results);
            };
            if (delay > 0) {
                delay = parseInt(Math.random() * delay);//随机延时
                setTimeout(func, delay);
            } else {
                func();
            }
        } catch (error) {
            console.log('delayer error ', error);
            reject(error);
        }
    });
}

/**
 * 分组并发执行
 * @param {Object} options 参数
 * 	{Array} options.items 请求参数列表 [{reqs:{url:'',exec:func}},...]
 * 	{number} options.limitInGroup 组内限制并发数
 * 	{number} options.retry 重试几次
 */
function grouper(options) {
    return new Promise(function (resolve, reject) {
        if (!options.items || options.items.length <= 0) {
            reject("grouper items empty!");
        }
        let succArray = [], trysArray = [], failArray = [];
        async.mapLimit(options.items, options.limitInGroup,
            async function (item, callback) {//集合中每个元素迭代该函数
                let res = await options.fetchor(item).catch(e => console.log(e));
                if (!res) {//没有给出返回结果，给定如下
                    res = { code: -1, reqs: item.reqs || {} };
                }

                if (res.code != 1) {//需要重试
                    if (item.retry) {
                        res.retry = item.retry + 1;
                    } else {
                        res.retry = options.retry - 1;
                    }
                    if (res.retry <= options.retry) {
                        trysArray.push(res);
                    } else {
                        console.log(`${options.id} fetch failed, item = ${JSON.stringify(item)}, after try ${options.retry} times!`);
                        failArray.push(res);
                    }
                } else {
                    succArray.push(res);
                }
                //此处callback是完成单个item做的事情，可选
                //callback(item, res);
            },
            function (err, results) {
                //所有item函数完成后或发生错误时触发的回调函数。results是单个item的callback传回结果的数组
                if (err) {
                    reject(err);
                }
                resolve({ succ: succArray, trys: trysArray, fail: failArray });
            });

    });//end promise
}

module.exports = fanner;